#include <GL/glew.h>
#include <GL/glut.h>
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <cmath>
#include "shader.h"
#include "draw.h"
#include "mesh.h"

unsigned range = 0xffffffff;
unsigned WINDOW_WIDTH = 400;
unsigned WINDOW_HEIGHT = 400;
Mesh mesh ;
float angle = 0.0;

STATES states; 


inline void triangle(Point p1, Point p2, Point p3, color_RGB & color, unsigned res)
{
								// Drawing Using Triangles
		glBegin(GL_TRIANGLES);
		glColor3f(color.r, color.g, color.b);

		Point n = Point::normal((p2-p1)^(p3-p1)) ;
		glNormal3f (n.x, n.y, n.z);
		glVertex3f( p1.x, p1.y, p1.z);				// Top
		glVertex3f( p2.x, p2.y, p2.z);				// Bottom Left
		glVertex3f( p3.x, p3.y, p3.z);				// Bottom Right
		glColor3f(randreal(), randreal() , randreal());
		glEnd();
		//Draw Resolution points; 
		if (states.sample_points)
		{
			for (unsigned i = 0 ; i <= res; ++i)
			{
				for (unsigned j = 0 ; j<= res-i; ++j)
				{
					Point p = p1 * (float (i)/ res) + p2 * (float (j)/ res) + p3 * (float(res-i-j)/res) ;
					glColor3f(1.0, 1.0, 0);
					drawsphere(p.x, p.y, p.z, 0.02); 
				}
			}
		}
		//drawsphere(1, 0.);
		
		
		
}

inline void drawTriangles (GLenum mode)
{
	unsigned count = 1;

	
	for (std::vector<Face *>::iterator iter = mesh.v_faces.begin(); iter != mesh.v_faces.end(); ++iter)
	{
		if (mode == GL_SELECT)
		glLoadName(count++);
		/*
		if (states.selected_face_idx - 1 == iter - mesh.v_faces.begin())
		{
			color_RGB c = (*iter)->color;
			c.r = 1.0 - c.r ;
			c.g = 1.0 - c.g ;
			c.b = 1.0 -c.b ;
			triangle((*iter)->he->from_v->point, (*iter)->he->next->from_v->point, (*iter)->he->next->next->from_v->point,
				c, (*iter)->resolution);
		}
		else
		{
		*/
			triangle((*iter)->he->from_v->point, (*iter)->he->next->from_v->point, (*iter)->he->next->next->from_v->point,
				(*iter)->color, (*iter)->resolution);
		//}
		//glColor3f(randreal(), randreal() , randreal());
		//glutSolidSphere(0.5, 2 , 2);
	}
	//glColor3f(1.0, 1.0, 0);
}


void init(void)
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);
	glDepthRange(0.0, 1.0);  /* The default z mapping */
}

void drawSence(GLenum mode)
{
	if (mode == GL_SELECT)
		glLoadName(1);
	/*
	glBegin(GL_QUADS);
	glColor3f(1.0, 1.0, 0.0);
	glVertex3i(2, 0, 0);
	glVertex3i(2, 6, 0);
	glVertex3i(6, 6, 0);
	glVertex3i(6, 0, 0);
	glEnd();

	*/
	drawTriangles(mode);
	//glutSolidCube(1);

}

void unmap_co(unsigned x, unsigned y, unsigned z)
{


	// This function will find 2 points in world space that are on the line into the screen defined by screen-space( ie. window-space ) point (x,y)
	double mvmatrix[16];
	double projmatrix[16];
	int viewport[4];
	double dX, dY, dZ, dClickY; // glUnProject uses doubles, but I'm using floats for these 3D vectors

	double dClickZ = double(z) / range; 
	glGetIntegerv(GL_VIEWPORT, viewport);	
	glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix);
	glGetDoublev (GL_PROJECTION_MATRIX, projmatrix);
	dClickY = double (WINDOW_HEIGHT - y); // OpenGL renders with (0,0) on bottom, mouse reports with (0,0) on top

	//double winx = double(x) / WINDOW_WIDTH;	
	gluUnProject ((double) x, dClickY, dClickZ , mvmatrix, projmatrix, viewport, &dX, &dY, &dZ);
	std::cout<<dX<<' '<<dY<<' '<<dZ<<std::endl;
}
unsigned processHits(GLuint hits, GLuint buffer[], GLuint x, GLuint y)
{
	unsigned int i, j;
	GLuint names, *ptr;
	unsigned idx = -1 ;
	unsigned zbuffer = range;
	//printf("hits = %d\n", hits);
	ptr = (GLuint *) buffer;
	for (i = 0; i < hits; i++) {  /* for each hit  */
		names = *ptr;
		unsigned zmin, zmax; 
		printf(" number of names for hit = %d\n", names); ptr++;
		zmin = (unsigned)(*ptr) ; ptr++ ;
		zmax = (unsigned)(*ptr) ; ptr++;
		//printf("  z1 is %u;", zmin); 
		//printf(" z2 is %u\n", zmax); 

		unmap_co(x, y, zmin);
		unmap_co(x,y, zmax);	
		if (zmin < zbuffer)
		{
			zbuffer = zmin; 
			idx = unsigned(*ptr); 
		}
		//printf("   the name is ");
		for (j = 0; j < names; j++) {  /* for each name */
			//printf("%d ", *ptr); 
			ptr++;
		}
		//printf("\n");
	}
	std::cout<<"The face picked is " <<idx <<std::endl;
	states.selected_face_idx = idx ;
	return  idx ;
}

#define BUFSIZE 512

void pickRects(int button, int state, int x, int y)
{
	std::cout<<"X "<<x<<' '<<"Y"<<y<<std::endl;
	GLuint selectBuf[BUFSIZE];
	GLint hits;
	GLint viewport[4];

	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
	{
		glGetIntegerv(GL_VIEWPORT, viewport);

		glSelectBuffer(BUFSIZE, selectBuf);
		(void) glRenderMode(GL_SELECT);

		glInitNames();
		glPushName(0);

		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		/*  create 5x5 pixel picking region near cursor location */
		gluPickMatrix((GLdouble) x, (GLdouble) (viewport[3] - y),
			5.0, 5.0, viewport);
		//glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5);
		drawSence(GL_SELECT);
		glPopMatrix();
		glFlush();

		hits = glRenderMode(GL_RENDER);
		processHits(hits, selectBuf, x, y);
	}
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	//glTranslatef(0.0f, 0.0f, -2.0f);
	glRotatef(angle, 0.0f, 1.0f, 1.0f); 
	drawSence(GL_RENDER);
	glFlush();
	glutSwapBuffers();
}

void processNormalKeys(unsigned char key, int x, int y) {

	switch (key)
	{
	case  's' : case 'S':
		states.sample_points = ! (states.sample_points); 
		std::cout<<"SAMPLE STATES "<<states.sample_points<<std::endl;
		break;
	case  'u' : case 'U':
		if (states.selected_face_idx > 0)
		{
			mesh.v_faces[states.selected_face_idx - 1]->resolution++;
		}
		break;
	case  'd' : case 'D':
		if (states.selected_face_idx > 0)
		{
			mesh.v_faces[states.selected_face_idx - 1]->resolution--;
			if ( (mesh.v_faces[states.selected_face_idx - 1]->resolution) < 1)
			{
				(mesh.v_faces[states.selected_face_idx - 1]->resolution) = 1;
			}
		}
		break;
	case 'r' : case 'R' :
		angle += 0.2; 
	}
	display();
}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	//glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

}
int main(int argc, char **argv)
{

	std::ifstream ifs("cube3.obj");
	ifs >> mesh; 
	std::cout<<mesh.edges.size() <<std::endl;
	mesh.prepare_color();
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize (WINDOW_WIDTH, WINDOW_HEIGHT);
	glutInitWindowPosition (100, 100);
	glutCreateWindow(argv[0]);
	init();
	glutMouseFunc(pickRects);
	glutKeyboardFunc(processNormalKeys);
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	
	/*
        glewInit();
        if (glewIsSupported("GL_VERSION_2_0"))
                printf("Ready for OpenGL 2.0\n");
        else {
                printf("OpenGL 2.0 not supported\n");
                exit(1);
        }

        setShaders();
	*/

	glutMainLoop();
	return 0; 
}
